package ee.ut.maxorato_macobo.oop.mud.game;

import java.util.ArrayList;
import java.util.ListIterator;
import java.util.Random;
import java.util.TreeMap;

import ee.ut.maxorato_macobo.oop.mud.items.ContainsItems;
import ee.ut.maxorato_macobo.oop.mud.items.ItemArmour;
import ee.ut.maxorato_macobo.oop.mud.items.ItemBase;

public abstract class GameCharacter implements ContainsItems {
	static long nextCharacterIndex = 1;
	
	protected GameRoom currentRoom;
	protected ArrayList<ItemBase> inventory;
	private long characterIndex;
	protected GameCharacter fightTarget;
	protected int targetLifeCycleIndex;
	protected Random randGen;
	
	protected boolean isSleeping;
	
	protected int currentEnergy;
	protected int currentMana;
	protected int currentHealth;
	
	protected int pierceDefense, bashDefense, slashDefense, magicDefense;
	
	protected TreeMap<String, ItemArmour> wornItems;
	protected EquipmentContainer equipmentContainer;
	
	public GameCharacter(GameRoom newCurrentRoom) {
		characterIndex = nextCharacterIndex++;
		
		isSleeping = false;
		
		pierceDefense = 0;
		bashDefense = 0;
		slashDefense = 0;
		magicDefense = 0;
		
		randGen = new Random(System.nanoTime() + characterIndex);
		
		wornItems = new TreeMap<String, ItemArmour>();
		wornItems.put("finger0", null);
		wornItems.put("finger1", null);
		wornItems.put("neck0", null);
		wornItems.put("neck1", null);
		wornItems.put("body0", null);
		wornItems.put("head0", null);
		wornItems.put("legs0", null);
		wornItems.put("feet0", null);
		wornItems.put("hands0", null);
		wornItems.put("arms0", null);
		wornItems.put("shield0", null);
		wornItems.put("abody0", null);
		wornItems.put("waist0", null);
		wornItems.put("wrist0", null);
		wornItems.put("wrist1", null);
		
		equipmentContainer = new EquipmentContainer(this);
		
		currentRoom = newCurrentRoom;
		
		fightTarget = null;
		
		inventory = new ArrayList<ItemBase>();
		
		if(currentRoom != null) {
			currentRoom.characterEnteredRoom(this);
		}
	}
	
	public long getIndex() {
		return characterIndex;
	}
	
	public void moveToRoom(GameRoom newRoom, String direction) {
		if(currentRoom != null) {
			currentRoom.characterLeftRoom(this, direction);
		}
		
		currentRoom = newRoom;
		currentRoom.characterEnteredRoom(this);
	}
	
	public GameRoom getRoom() {
		return currentRoom;
	}
	
	public String canAddItem(ItemBase item) {
		return null;
	}
	
	public void itemAdd(ItemBase item) {
		if(!inventory.contains(item)) {
			inventory.add(item);
		}
	}
	
	public void itemRemove(ItemBase item) {
		if(inventory.contains(item)) {
			inventory.remove(item);
		}
	}
	
	public ArrayList<ItemBase> getItemList() {
		return inventory;
	}
	
	public ItemBase findItemByName(String itemName) {
		return findItemByName(itemName, 1);
	}
	
	public ItemBase findItemByName(String itemName, int index) {
		ListIterator<ItemBase> it = getItemList().listIterator();
		int matchCount = 0;
		
		while(it.hasNext()) {
			ItemBase item = it.next();
			
			if(item.getName().compareToIgnoreCase(itemName) == 0) {
				
				if(++matchCount == index) {
					return item;
				}
			}
		}
		
		return null;
	}
	
	public void validateFight() {
		if(fightTarget != null && !fightTarget.isAlive(targetLifeCycleIndex)) {
			//PlayerPool.messageToAll(this.getCharName() + " targeting " + fightTarget.getCharName() + " invalidated, not alive.");
			fightTarget = null;
		}
		else if(fightTarget != null && fightTarget.getRoom() != getRoom()) {
			//PlayerPool.messageToAll(this.getCharName() + " targeting " + fightTarget.getCharName() + " invalidated, not in room.");
			fightTarget = null;
		}
	}
	
	public void showAttackMessage(GameCharacter target, String hitType, int hitValue) {
		for(PlayerCharacter player : getRoom().getRoomPlayerList()) {
			String source = (this == player) ? "Your" : GameUtils.stringCapitalize(this.getCharName()) + "'s";
			String dest = (target == player) ? "you" : target.getCharName();
			String colourTag = (this == player) ? "\033[1;35m" : "\033[1;36m", colourEndTag = "\033[0m";
			
			if(player != target && player != this && hitValue < 1) continue;
			
			if(hitValue < 0) player.sendTickMessage(source + " " + hitType + "\033[0m was blocked by " + dest + ".");
			else if(hitValue == 0) player.sendTickMessage(source + " " + hitType + "\033[0m missed " + dest + ".");
			else player.sendTickMessage(colourTag + source + " " + hitType + colourTag + " hits " + dest + " with " + hitValue + " damage." + colourEndTag);
		}
	}
	
	public void applyHitDamageFrom(GameCharacter sourceChar, int hitValue) {
		gotAttackedBy(sourceChar);
		
		if(hitValue < 1) return;
		
		currentHealth = Math.max(0, getHealth() - hitValue);
		
		if(currentHealth == 0) {
			for(PlayerCharacter player : getRoom().getRoomPlayerList()) {
				String source = (sourceChar == player) ? "You" : sourceChar.getCharName();
				String dest = (this == player) ? "you" : this.getCharName();
				String colourTag = (this == player || sourceChar == player) ? "\033[1;31m" : "\033[0m", colourEndTag = "\033[0m";
				
				if(this == player) player.sendTickMessage(colourTag + dest + " were killed by " + source + "." + colourEndTag);
				else player.sendTickMessage(colourTag + source + " killed " + dest + "." + colourEndTag);
			}
			
			handleDeath(false);
		}
	}
	
	public void processFightTick() {
		validateFight();
		
		if(fightTarget == null) return;
		
		int hits = fightTarget.getNumberOfHits(this);
		
		for(int i = 0; i < hits; i++) {
			int hitValue = getHitValue(fightTarget);
			
			showAttackMessage(fightTarget, "attack", hitValue);

			fightTarget.applyHitDamageFrom(this, hitValue);
			
			validateFight();
			
			if(fightTarget == null) break;
		}
	}
	
	public void attackTarget(GameCharacter target) {
		fightTarget = target;
		targetLifeCycleIndex = fightTarget.getLifeCycleIndex();
		
		target.gotAttackedBy(this);
	}
	
	public GameCharacter getFightTarget() {
		return fightTarget;
	}
	
	public void gotAttackedBy(GameCharacter aggressor) {
		if(fightTarget != null) return;
		
		if(isSleeping) {
			if(this instanceof PlayerCharacter) {
				((PlayerCharacter)this).sendTickMessage("You wake up because you're under attack.");
			}
			
			isSleeping = false;
		}
		
		if(hasAutoRetaliate()) {
			fightTarget = aggressor;
			targetLifeCycleIndex = fightTarget.getLifeCycleIndex();
			
			if(fightTarget.getFightTarget() == null && fightTarget.hasAutoRetaliate()) {
				fightTarget.gotAttackedBy(this);
			}
		}
	}
	
	public String getBriefStatusDesc() {
		double percent = (double)getHealth() / (double)getMaxHealth() * 100.0;
		String statusDesc = "";
		
		if(isSleeping) {
			statusDesc += "(\033[1;37mSleeping\033[0m)";
		}
		
		if(percent < 5.0) {
			statusDesc += "(\033[1;31mBleeding\033[0m)";
		}
		else if(percent < 60.0) {
			statusDesc += "(\033[0;31mWounded\033[0m)";
		}
		
		if(statusDesc.length() > 0) statusDesc += " ";
		
		return statusDesc;
	}
	
	public String getSlotForArmour(ItemArmour testItem) {
		if(wornItems.containsKey(testItem.getWearLocation() + "0") && wornItems.get(testItem.getWearLocation() + "0") == null) {
			return testItem.getWearLocation() + "0";
		}
		else if(wornItems.containsKey(testItem.getWearLocation() + "1") && wornItems.get(testItem.getWearLocation() + "1") == null) {
			return testItem.getWearLocation() + "1";
		}
		else {
			return null;
		}
	}
	
	public void wearItem(ItemArmour testItem) {
		String slotName = getSlotForArmour(testItem);
		
		if(slotName == null) return;
		
		wornItems.put(slotName, testItem);
		testItem.moveToLocation(equipmentContainer);
		
		calculateDefenses();
	}
	
	public void removeItem(ItemArmour testItem) {
		if(wornItems.get(testItem.getWearLocation() + "0") == testItem) {
			wornItems.put(testItem.getWearLocation() + "0", null);
		}
		else if(wornItems.get(testItem.getWearLocation() + "1") == testItem) {
			wornItems.put(testItem.getWearLocation() + "1", null);
		}
		else {
			return;
		}
		
		equipmentContainer.itemRemove(testItem);
		calculateDefenses();
	}
	
	public void calculateDefenses() {
		pierceDefense = 0;
		bashDefense = 0;
		slashDefense = 0;
		magicDefense = 0;
		
		for(ItemArmour armour : wornItems.values()) {
			if(armour == null) continue;
			
			pierceDefense += armour.getPierceDefBonus();
			bashDefense += armour.getBashDefBonus();
			slashDefense += armour.getSlashDefBonus();
			magicDefense += armour.getMagicDefBonus();
		}
	}
	
	public int getDefenseAgainstPierce() {
		return pierceDefense;
	}
	
	public int getDefenseAgainstBash() {
		return bashDefense;
	}
	
	public int getDefenseAgainstSlash() {
		return slashDefense;
	}
	
	public int getDefenseAgainstMagic() {
		return magicDefense;
	}
	
	public void describeEquipmentToPlayer(PlayerCharacter player) {
		for(ItemArmour armour : wornItems.values()) {
			if(armour == null) continue;
			
			String position = "";
			
			if(armour.getWearLocation().equals("neck")) {
				position = "<worn around neck>";
			}
			else if(armour.getWearLocation().equals("shield")) {
				position = "<worn as shield>";
			}
			else if(armour.getWearLocation().equals("abody")) {
				position = "<worn about body>";
			}
			else if(armour.getWearLocation().equals("waist")) {
				position = "<worn about waist>";
			}
			else if(armour.getWearLocation().equals("wrist")) {
				position = "<worn around wrist>";
			}
			else {
				position = "<worn on " + armour.getWearLocation() + ">";
			}
			
			for(int i = 0, x = 20 - position.length(); i < x; i++) {
				position = position + " ";
			}
			
			player.sendTickMessage(position + armour.getLongName());
		}
	}
	
	public int getHitValue(GameCharacter target) {
		int chanceToBlock = (this instanceof PlayerCharacter) ? 150 : 250;
		int chanceToMiss = (this instanceof PlayerCharacter) ? 250 : 350;
		
		chanceToBlock += target.getDefenseAgainstBash() * 5;
		chanceToMiss += target.getDefenseAgainstBash() * 5;
		
		if(randGen.nextInt(1000) < chanceToBlock) return -1;
		else if(randGen.nextInt(1000) < chanceToMiss) return 0;
		
		int finalHit = (int)((double)(randGen.nextInt(15) + 1) * Math.max(0.2, 1.0 - target.getDefenseAgainstBash() / 100.0));
		
		return finalHit;
	}
	
	public abstract int getNumberOfHits(GameCharacter target);
	
	public abstract void handleDeath(boolean defaultNotice);
	public abstract boolean isAlive(int lifeCycleIndex);
	public abstract int getLifeCycleIndex();
	public abstract boolean hasAutoRetaliate();
	public abstract String getCharName();
	public abstract int getMaxEnergy();
	public abstract int getMaxMana();
	public abstract int getMaxHealth();
	
	public void setEnergy(int newEnergy) {
		currentEnergy = newEnergy;
	}
	
	public int getEnergy() {
		return currentEnergy;
	}
	
	public void setMana(int newMana) {
		currentMana = newMana;
	}
	
	public int getMana() {
		return currentMana;
	}
	
	public void setHealth(int newHealth) {
		currentHealth = newHealth;
	}
	
	public int getHealth() {
		return currentHealth;
	}
	
	public boolean getIsSleeping() {
		return isSleeping;
	}
	
	public void setIsSleeping(boolean newIsSleeping) {
		isSleeping = newIsSleeping;
	}
	
	
	
	public class EquipmentContainer implements ContainsItems {
		private ArrayList<ItemBase> equipmentList;
		private GameCharacter listOwner;
		
		public EquipmentContainer(GameCharacter owner) {
			listOwner = owner;
			
			equipmentList = new ArrayList<ItemBase>();
		}
		
		public GameCharacter getListOwner() {
			return listOwner;
		}
		
		public String canAddItem(ItemBase item) {
			return null;
		}
		
		public void itemAdd(ItemBase item) {
			if(!equipmentList.contains(item)) {
				equipmentList.add(item);
			}
		}
		
		public void itemRemove(ItemBase item) {
			if(equipmentList.contains(item)) {
				equipmentList.remove(item);
				
				if(item instanceof ItemArmour) {
					GameCharacter.this.removeItem((ItemArmour)item);
				}
			}
		}
		
		public ArrayList<ItemBase> getItemList() {
			return equipmentList;
		}
		
		public ItemBase findItemByName(String itemName) {
			return findItemByName(itemName, 1);
		}
		
		public ItemBase findItemByName(String itemName, int index) {
			ListIterator<ItemBase> it = getItemList().listIterator();
			int matchCount = 0;
			
			while(it.hasNext()) {
				ItemBase item = it.next();
				
				if(item.getName().compareToIgnoreCase(itemName) == 0) {
					
					if(++matchCount == index) {
						return item;
					}
				}
			}
			
			return null;
		}
	}
}
